/*
Author: Marek Skrajnowski
Date: 09.11.2011

Memory Manager Singleton class.
*/

#ifndef MEMORY_MANAGER_H
#define MEMORY_MANAGER_H

#include <vector>
#include <deque>
#include <cassert>

#include "Singleton.h"
#include "BufferWrapper.h"

typedef std::vector<size_t> SizeVector;

// MemoryManager - manages a set of different buffers and handles to data stored in them
template<typename HANDLE = Handle<> > 
class MemoryManager 
	: public Singleton< MemoryManager<HANDLE> >
{
public:
	typedef HANDLE Handle;
	typedef std::vector<BufferWrapper*> BufferVector;
	typedef std::vector<bufferId_t> BufferIdVector;
	typedef std::deque<bufferId_t> BufferIdQueue;	

private:

	BufferVector m_Buffers;			// list of buffer wrappers
	BufferIdQueue m_FreeBuffersQueue;	// queue of free buffer ids
	
	// prepares a wrapper for a new buffer
	bufferId_t acquireBuffer();

public:
	
	// constructor
	MemoryManager();
	// destructor
	~MemoryManager();

	// create*Buffer functions create buffers of different types:

	// creates a pool-based buffer of elementSize*capacity bytes
	bufferId_t createPoolBuffer(size_t elementSize, size_t capacity);

	// creates a stack-based buffer of elementSize*capacity bytes
	bufferId_t createStackBuffer(size_t byteAlignment, size_t capacity);
	
	// creates an empty managed pool-based buffer
	//bufferId_t createManagedPoolBuffer();
	// adds a pool to a managed pool-based buffer
	//void addPoolToManagedBuffer(bufferId_t managedId, size_t elementSize, size_t capacity);

	// creates a managed pool-based buffer by creating and wrapping a set of single pool-based buffers
	bufferId_t createManagedPoolBuffer(const SizeVector& elementSizes, const SizeVector& capacities);
			
	// removes a buffer
	void releaseBuffer(bufferId_t bufferIndex);
	
	// accessing data:

	// creates a new object in the buffer specified
	template<typename DATA> DATA* acquire(bufferId_t bufferId, HANDLE& handle);

	// fetches a pointer to an object referenced by the handle
	template<typename DATA> DATA* dereference(HANDLE handle);

	// frees memory used by the object referenced by the handle
	// for Stack-based strategy, it will also destroy all the objects "above" the one referenced by the handle
	void release(HANDLE handle);

};


template<typename HANDLE>
MemoryManager<HANDLE>::MemoryManager() {
}

template<typename HANDLE>
MemoryManager<HANDLE>::~MemoryManager() {
	for(unsigned int i=0; i<m_Buffers.size(); ++i) {
		delete m_Buffers[i];
	}
}

template<typename HANDLE>
bufferId_t MemoryManager<HANDLE>::acquireBuffer() {
	if(m_FreeBuffersQueue.empty()) {
		assert(m_Buffers.size() < HANDLE::MAX_BUFFER_INDEX + 1 && "More buffers than handle buffer index can handle.");
			
		m_FreeBuffersQueue.push_back(m_Buffers.size());
		m_Buffers.push_back(new BufferWrapper());
	}

	bufferId_t id = m_FreeBuffersQueue.back();
	m_FreeBuffersQueue.pop_back();
	return id;
}


// create*Buffer functions create buffers of different types:

// creates a pool-based buffer of elementSize*capacity bytes
template<typename HANDLE>	
bufferId_t MemoryManager<HANDLE>::createPoolBuffer(size_t elementSize, size_t capacity) {
	bufferId_t id = acquireBuffer();
	m_Buffers[id]->createPoolBuffer(elementSize, capacity);
	return id;
}

// creates a stack-based buffer of elementSize*capacity bytes
template<typename HANDLE>
bufferId_t MemoryManager<HANDLE>::createStackBuffer(size_t byteAlignment, size_t capacity) {
	bufferId_t id = acquireBuffer();
	m_Buffers[id]->createStackBuffer(byteAlignment, capacity);
	return id;
}
	
// creates an empty managed pool-based buffer
//bufferId_t createManagedPoolBuffer();
// adds a pool to a managed pool-based buffer
//void addPoolToManagedBuffer(bufferId_t managedId, size_t elementSize, size_t capacity);

// creates a managed pool-based buffer by creating and wrapping a set of single pool-based buffers
template<typename HANDLE>
bufferId_t MemoryManager<HANDLE>::createManagedPoolBuffer(const SizeVector& elementSizes, const SizeVector& capacities) {
	bufferId_t id = acquireBuffer();
	assert(elementSizes.size() == capacities.size() && "elementSizes and capacities vector sizes dont match");

	m_Buffers[id]->createManagedBuffer();

	for(unsigned int i=0; i<elementSizes.size(); ++i) {
		m_Buffers[id]->addPoolToManage(elementSizes[i], capacities[i]);
	}

	return id;
}
			
// removes a buffer
template<typename HANDLE>
void MemoryManager<HANDLE>::releaseBuffer(bufferId_t bufferIndex) {
	assert(bufferIndex < m_Buffers.size());
	m_Buffers[bufferIndex]->free();
	m_FreeBuffersQueue.push_back(bufferIndex);
}
	
// accessing data:

// creates a new object in the buffer specified
template<typename HANDLE>
template<typename DATA> DATA* MemoryManager<HANDLE>::acquire(bufferId_t bufferId, HANDLE& handle) {
	dataId_t dataId = 0;

	assert(bufferId < m_Buffers.size() && "Buffer id overflow");
	
	DATA* dataPointer = m_Buffers[bufferId]->acquire<DATA>(dataId);
	handle.init(bufferId, dataId);
	m_Buffers[bufferId]->setMagic(dataId, handle.getMagic());

	return dataPointer;
}

// fetches a pointer to an object referenced by the handle
template<typename HANDLE>
template<typename DATA> DATA* MemoryManager<HANDLE>::dereference(HANDLE handle) {
	if ( handle.isNull() )  return ( 0 );
	assert(handle.getBufferIndex() < m_Buffers.size());

	return m_Buffers[handle.getBufferIndex()]->dereference<DATA>(handle.getDataIndex(), handle.getMagic());
}

// frees memory used by the object referenced by the handle
// for Stack-based strategy, it will also destroy all the objects "above" the one referenced by the handle
template<typename HANDLE>
void MemoryManager<HANDLE>::release(HANDLE handle) {
	if ( handle.isNull() )  return;
	assert(handle.getBufferIndex() < m_Buffers.size());

	return m_Buffers[handle.getBufferIndex()]->release(handle.getDataIndex(), handle.getMagic());
}

#endif

